/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git.refer;

import java.io.File;
import java.io.IOException;

import org.zhiqim.git.GitConstants;
import org.zhiqim.git.GitRefer;
import org.zhiqim.git.GitWalker;
import org.zhiqim.git.objer.model.PersonIdent;
import org.zhiqim.git.objer.objectid.ObjectId;
import org.zhiqim.git.util.GitValidates;
import org.zhiqim.git.util.Gits;

import org.zhiqim.kernel.util.Files;
import org.zhiqim.kernel.util.Strings;

/**
 * 引用重命名管理器，工作原理是先将源引用重命名为临时名称，然后再将临时名称重命名为最终目标引用。
 *
 * @version v1.0.0 @author zouzhigang 2017-4-13 新建与整理
 */
public class RefRenamer implements GitConstants
{
    private final GitRefer refer;
    private final RefUpdater src;
    private final RefUpdater dst;

    /************************************************************************************/
    //构造函数&基础参数获取
    /************************************************************************************/
    
    public RefRenamer(RefUpdater src, RefUpdater dst)
    {
        this.refer = src.getRefer();
        this.src = src;
        this.dst = dst;
        
        //分枝日志加上前缀Branch: 
        String cmd = (src.getName().startsWith(P_REFS_HEADS_) && dst.getName().startsWith(P_REFS_HEADS_))?"Branch: ":"";
        setRefLogMessage(cmd + "renamed " + Gits.shortenRefName(src.getName()) + " to " + Gits.shortenRefName(dst.getName()));
    }
    
    /************************************************************************************/
    //参数获取&设置
    /************************************************************************************/

    public PersonIdent getRefLogIdent()
    {
        return dst.getRefLogIdent();
    }
    
    public void setRefLogIdent(PersonIdent pi)
    {
        dst.setRefLogIdent(pi);
    }
    
    /** 设置日志信息，但不增加结果信息 */
    public void setRefLogMessage(String msg)
    {
        if (msg == null)
            disableRefLog();
        else
            dst.setRefLogMessage(msg, false);
    }

    /** 不记录日志 */
    public void disableRefLog()
    {
        dst.disableRefLog();
    }
    
    /************************************************************************************/
    //重命名
    /************************************************************************************/
    
    /**
     * 重命名操作，根据传入的src/dst进行处理
     * 
     * @return              重命名结果
     * @throws IOException  操作异常
     */
    public RefUpdateResult rename() throws IOException
    {
        if (src.getRef().isSymbolic())
        {//快捷方式不支持重命名
            return RefUpdateResult.IO_FAILURE;
        }
        
        if (GitValidates.isCurrentBranche(refer, src.getRef()))
        {//当前分枝不支持重命名
            return RefUpdateResult.REJECTED_CURRENT_BRANCH;
        }
        
        //判断当前分枝是否需要重指向
        ObjectId oid = src.getOldObjectId();
        GitWalker walker = new GitWalker(refer.git());
        
        RefUpdater tmp = refer.newTemporaryUpdater();
        
        try
        {
            //1.先备份原引用
            tmp.setNewObjectId(oid);
            tmp.setForceUpdate(true);
            tmp.disableRefLog();
            switch (tmp.update(walker))
            {
            case NEW:
            case FORCED:
            case NO_CHANGE:
                break;
            default:
                return tmp.getResult();
            }

            //2.重命名原日志文件名称到临时引用日志
            if (!renameLog(src, tmp))
                return RefUpdateResult.IO_FAILURE;

            //3.删除原引用
            src.setExpectedOldObjectId(oid);
            src.setForceUpdate(true);
            src.disableRefLog();
            if (src.delete(walker) != RefUpdateResult.FORCED)
            {//删除失败，重命名日志文件回原引用日志，返回结果
                renameLog(tmp, src);
                return src.getResult();
            }

            //4.先重命名引用日志文件到目标名称
            if (!renameLog(tmp, dst))
            {//如果失败，重命名日志文件回原引用日志，重建原引用
                renameLog(tmp, src);
                src.setExpectedOldObjectId(ZEROID);
                src.setNewObjectId(oid);
                src.update(walker);
                return RefUpdateResult.IO_FAILURE;
            }

            //5.创建目标引用
            dst.setExpectedOldObjectId(ZEROID);
            dst.setNewObjectId(oid);
            if (dst.update(walker) != RefUpdateResult.NEW)
            {//创建失败，恢复原引用日志文件和重建原引用
                if (renameLog(dst, tmp))
                    renameLog(tmp, src);
                src.setExpectedOldObjectId(ZEROID);
                src.setNewObjectId(oid);
                src.update(walker);
                return dst.getResult();
            }

            return RefUpdateResult.RENAMED;
        }
        finally
        {
            try
            {
                refer.delete(tmp);
            }
            catch (IOException err)
            {
                refer.fileFor(tmp.getName()).delete();
            }
        }
    }

    /** 重命名日志文件 */
    private boolean renameLog(RefUpdater src, RefUpdater dst)
    {
        File srcLog = refer.logFor(src.getName());
        File dstLog = refer.logFor(dst.getName());

        if (!srcLog.exists())
            return true;

        if (!rename(srcLog, dstLog))
            return false;

        try
        {
            int depth = Strings.getTimes(src.getName(), '/') - 2;
            Files.deleteFile(srcLog, depth);
            return true;
        }
        catch (IOException e)
        {
            rename(dstLog, srcLog);
            return false;
        }
    }
    
    /** 重命名引用文件 */
    private boolean rename(File src, File dst)
    {
        if (src.renameTo(dst))
            return true;

        File dir = dst.getParentFile();
        if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory())
            return false;
        
        return src.renameTo(dst);
    }
}
